package com.developer.platform.infrastructure.security;

import cn.hutool.core.collection.CollUtil;
import com.developer.platform.domain.security.AuthenticationInfo;
import com.developer.platform.domain.service.IAuthenticationSource;
import com.developer.platform.domain.service.IAuthorizationSource;
import com.developer.platform.infrastructure.security.handler.ErrorHandler;
import com.veeker.core.exceptions.BusinessException;
import java.io.IOException;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

/**
 * @author ：qiaoliang
 * @date ：2020-11-06
 */
@Component
public class AuthenticationFilter extends BasicAuthenticationFilter {

    private final IAuthorizationSource authorizationResource;
    private final IAuthenticationSource authenticationSource;

    public AuthenticationFilter(AuthenticationManager authenticationManager,
                                IAuthorizationSource authorizationResource,
                                IAuthenticationSource authenticationSource) {
        super(authenticationManager);
        this.authorizationResource = authorizationResource;
        this.authenticationSource = authenticationSource;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String token = request.getHeader(HttpHeaders.AUTHORIZATION);

        if(!isMatcherAllowedRequest(request)){
            try {
                AuthenticationInfo authenticationInfo = authenticationSource.loadAuthentication(token);
                // 认证成功，将完全认证的Authentication authentication设置到 SecurityContextHolder
                // 中的 SecurityContext 上。
                SecurityContextHolder.getContext().setAuthentication(authenticationInfo);
            }catch (BusinessException e){
                // 认证失败，清除 SecurityContextHolder 的安全上下文
                SecurityContextHolder.clearContext();
                ErrorHandler.forward(request,response,e);
                return;
            }
        }

        // 如果当前请求并非含有 http authentication 头部的请求，则直接放行，继续filter chain的执行
        chain.doFilter(request, response);
    }


    /**
     * 判断当前请求是否在允许请求的范围内
     * @param request 当前请求
     * @return 是否在范围中
     */
    private boolean isMatcherAllowedRequest(HttpServletRequest request){
        //  不需要安全认证地址
        List<String> pathPatterns = authorizationResource.excludePathPatterns();
        return CollUtil.isNotEmpty(pathPatterns) && pathPatterns.stream().map(AntPathRequestMatcher::new)
                .filter(requestMatcher -> requestMatcher.matches(request))
                .toArray().length > 0;
    }

}
